package com.dz.gateway.filter;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.GZIPInputStream;

import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.springframework.http.MediaType;
import org.springframework.util.ReflectionUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dz.common.web.ResultVO;
import com.netflix.util.Pair;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.constants.ZuulHeaders;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.util.HTTPRequestUtils;

@Slf4j
public class ResponseFilter extends ZuulFilter {

	private ThreadLocal<byte[]> buffers = new ThreadLocal<byte[]>() {
		@Override
		protected byte[] initialValue() {
			return new byte[8192];
		}
	};

	@Override
	public boolean shouldFilter() {
		return !RequestContext.getCurrentContext().getZuulResponseHeaders().isEmpty()
				|| RequestContext.getCurrentContext().getResponseDataStream() != null
				|| RequestContext.getCurrentContext().getResponseBody() != null;
	}

	private void writeResponse() throws Exception {
		RequestContext context = RequestContext.getCurrentContext();
		// there is no body to send
		if (context.getResponseBody() == null && context.getResponseDataStream() == null) {
			return;
		}
		HttpServletResponse servletResponse = context.getResponse();
		if (servletResponse.getCharacterEncoding() == null) { // only set if not
			servletResponse.setCharacterEncoding("UTF-8");
		}
		OutputStream outStream = servletResponse.getOutputStream();
		InputStream is = null;
		try {
			if (context.getResponseBody() != null) {
				String body = context.getResponseBody();
				writeResponse(new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding())),
						outStream, false);
				return;
			}
			boolean isGzipRequested = false;
			final String requestEncoding = context.getRequest().getHeader(ZuulHeaders.ACCEPT_ENCODING);
			final String contntType = context.getResponse().getContentType();
			boolean writeJson = contntType.startsWith(MediaType.APPLICATION_JSON_VALUE);
			if (requestEncoding != null && HTTPRequestUtils.getInstance().isGzipped(requestEncoding)) {
				isGzipRequested = true;
			}
			final String refer = context.getRequest().getHeader(org.springframework.http.HttpHeaders.REFERER);
			if (refer != null && refer.endsWith("swagger-ui.html")) {
				writeJson = false;
			}
			is = context.getResponseDataStream();
			InputStream inputStream = is;
			if (is != null) {
				if (context.sendZuulResponse()) {
					// if origin response is gzipped, and client has not
					// requested gzip,
					// decompress stream
					// before sending to client
					// else, stream gzip directly to client
					if (context.getResponseGZipped() && !isGzipRequested) {
						// If origin tell it's GZipped but the content is ZERO
						// bytes,
						// don't try to uncompress
						final Long len = context.getOriginContentLength();
						if (len == null || len > 0) {
							try {
								inputStream = new GZIPInputStream(is);
							} catch (java.util.zip.ZipException ex) {
								log.debug("gzip expected but not " + "received assuming unencoded response "
										+ RequestContext.getCurrentContext().getRequest().getRequestURL().toString());
								inputStream = is;
							}
						} else {
							// Already done : inputStream = is;
						}
					} else if (context.getResponseGZipped() && isGzipRequested) {
						servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
					}
					writeResponse(inputStream, outStream, writeJson);
				}
			} else {
				ResultVO resultVO = new ResultVO(500, null, "cant get response from server");
				writeResponse(new ByteArrayInputStream(JSON.toJSONBytes(resultVO)), outStream, false);
			}
		} finally {
			/**
			 * Closing the wrapping InputStream itself has no effect on closing
			 * the underlying tcp connection since it's a wrapped stream. I
			 * guess for http keep-alive. When closing the wrapping stream it
			 * tries to reach the end of the current request, which is
			 * impossible for infinite http streams. So instead of closing the
			 * InputStream we close the HTTP response.
			 *
			 * @author Johannes Edmeier
			 */
			try {
				Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse");
				if (zuulResponse instanceof Closeable) {
					((Closeable) zuulResponse).close();
				}
				outStream.flush();
				// The container will close the stream for us
			} catch (IOException ex) {
				log.warn("Error while sending response to client: " + ex.getMessage());
			}
		}
	}

	@Override
	public String filterType() {
		return "post";
	}

	@Override
	public int filterOrder() {
		return 500;
	}

	@Override
	public Object run() {
		try {
			addResponseHeaders();
			writeResponse();
		} catch (Exception ex) {
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}

	private void writeResponse(InputStream zin, OutputStream out, boolean writeJson) throws Exception {
		byte[] bytes = buffers.get();
		int bytesRead = -1;
		if (writeJson) {
			ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
			while ((bytesRead = zin.read(bytes)) != -1) {
				tempOut.write(bytes, 0, bytesRead);
			}
			String json = tempOut.toString();
			tempOut.close();
			Object retJSON = null;
			try {
				retJSON = JSON.parse(json);
			} catch (Exception ex) {
				log.error(ex.getMessage(), ex);
			}
			JSONObject outJSON = new JSONObject();
			if (retJSON == null) {
				outJSON.put("code", 200);
				outJSON.put("data", json);
			} else {
				if (retJSON instanceof JSONObject) {
					JSONObject jsonObject = (JSONObject) retJSON;
					if (!jsonObject.containsKey("code") && jsonObject.getIntValue("code") != 500) {
						outJSON.put("code", 200);
						outJSON.put("data", retJSON);
					}
				} else {
					outJSON.put("code", 200);
					outJSON.put("data", retJSON);
				}
			}
			ByteArrayInputStream in = null;
			if (outJSON.size() > 0) {
				String outString = outJSON.toString();
				in = new ByteArrayInputStream(outString.getBytes("utf-8"));
			} else {
				String outString = retJSON.toString();
				in = new ByteArrayInputStream(outString.getBytes("utf-8"));
			}
			while ((bytesRead = in.read(bytes)) != -1) {
				out.write(bytes, 0, bytesRead);
			}
		} else {
			while ((bytesRead = zin.read(bytes)) != -1) {
				out.write(bytes, 0, bytesRead);
			}
		}
	}

	private void addResponseHeaders() {
		RequestContext context = RequestContext.getCurrentContext();
		HttpServletResponse servletResponse = context.getResponse();
		List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
		@SuppressWarnings("unchecked")
		List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
		if (rd != null) {
			StringBuilder debugHeader = new StringBuilder();
			for (String it : rd) {
				debugHeader.append("[[[" + it + "]]]");
			}
		}
		if (zuulResponseHeaders != null) {
			for (Pair<String, String> it : zuulResponseHeaders) {
				servletResponse.addHeader(it.first(), it.second());
			}
		}
	}
}
